package com.gsy.aop;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.gsy.aop.annotation.NotRequired;
import com.gsy.aop.annotation.Optional;
import com.gsy.aop.annotation.ParameterValid;
import com.gsy.dto.BasePageSearchDto;
import com.gsy.exception.ApiParameterException;
import com.gsy.request.BaseRequest;
import com.gsy.request.PageSearchRequest;
import com.gsy.utils.JsonUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * 参数验证
 *
 * @author hthuang
 * @date 2018年11月20日
 */
@Aspect
@Component
@SuppressWarnings("static-method")
public class ParameterValidAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(ParameterValidAspect.class);


    @Pointcut("@annotation(com.gsy.aop.annotation.ParameterValid)")
    public void valid() {
    }

    /**
     * 验证处理
     *
     * @param joinPoint
     */
    @Before("valid()")
    public void validProcess(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        ParameterValid valid = method.getAnnotation(ParameterValid.class);
        int position = valid.position();
        if (args.length - 1 < position)
            throw new IllegalArgumentException("illegal ParameterValid's postion value beyond method's args length");
        Object thisObject = args[position];
        if (thisObject == null)
            throw new IllegalArgumentException("parameter is null");
        String requestJson = thisObject.toString();
        if (StringUtils.isBlank(requestJson))
            throw new IllegalArgumentException("parameter is null");
        String apiName = valid.apiName();
        if (valid.formatObj().getName().indexOf("com.gsy.dto.request.file.Upload") < 0)
            LOGGER.info("接收到{}请求：{}", apiName, requestJson);
        BaseRequest request = null;
        try {
            request = JsonUtils.parse(requestJson, valid.formatObj());
        } catch (Exception e) {
            LOGGER.error(MessageFormat.format("转换接口{0}请求数据异常", apiName), e);
        }
        if (request == null) {
            throw new IllegalArgumentException("转换接口请求数据异常");
        }
        Class<?> clazz = null;
        try {
            clazz = Class.forName(request.getClass().getName());
        } catch (ClassNotFoundException e) {
            LOGGER.error("对象加载异常，异常信息={}", e.getMessage());
            return;
        }
        String validResult = validField(request, clazz);
        if (StringUtils.isNotBlank(validResult)) {
            throw new ApiParameterException(validResult);
        }

        //分页验证
        if (request instanceof PageSearchRequest) {
            PageSearchRequest pageRequest = (PageSearchRequest) request;
            pageRequest.setPageNo(getPageVal(pageRequest.getPageNo(), BasePageSearchDto.DEFAULT_PAGE_NO));
            pageRequest.setPageSize(getPageVal(pageRequest.getPageSize(), BasePageSearchDto.DEFAULT_PAGE_SIZE));
        }
    }

    private Integer getPageVal(Integer pageIndex, Integer defaultVal) {
        return pageIndex.intValue() <= 0 ? defaultVal : pageIndex;
    }

    /**
     * 具体字段验证实现
     *
     * @param thisObject
     * @param clazz
     * @return
     */
    private String validField(Object thisObject, Class<?> clazz) {
        Field[] parentFields = clazz.getSuperclass().getDeclaredFields();
        Field[] chirldFields = clazz.getDeclaredFields();
        Field[] fields = new Field[parentFields.length + chirldFields.length];
        System.arraycopy(parentFields, 0, fields, 0, parentFields.length);
        System.arraycopy(chirldFields, 0, fields, parentFields.length, chirldFields.length);
        List<String> errorFields = Lists.newLinkedList();
        Map<String, Map<String, Object>> optionalMap = Maps.newHashMap();
        final String nullStr = " 为空";
        for (Field field : fields) {
            Annotation[] annotations = field.getAnnotations();
            if (ArrayUtils.isEmpty(annotations)) {
                continue;
            }
            try {
                field.setAccessible(true);

//                String errorField = buildApiFiledName(field.getName()) + nullStr;
                String errorField = field.getName() + nullStr;
                if (ObjectUtils.isEmpty(field.get(thisObject))) {
                    errorFields.add(errorField);
                }

                for (Annotation annotation : annotations) {
                    if (annotation instanceof Optional) {
                        String flag = ((Optional) annotation).flag();
                        Map<String, Object> map = optionalMap.get(flag);
                        if (MapUtils.isEmpty(map)) {
                            map = Maps.newHashMap();
                        }
                        map.put(field.getName(), field.get(thisObject));
                        optionalMap.put(flag, map);
                        errorFields.remove(errorField);
                    }
                    if (annotation instanceof NotRequired) {
                        if (ObjectUtils.isEmpty(field.get(thisObject))) {
                            errorFields.remove(errorField);
                        }

                    }
                }
            } catch (IllegalArgumentException | IllegalAccessException e) {
                LOGGER.error("获取字段值发生异常，异常信息={}", e.getMessage());
            } finally {
                field.setAccessible(false);
            }
        }
        return buildValidResult(errorFields, optionalMap, nullStr);
    }

    /**
     * 构建验证结果
     *
     * @param errorFields 错误字段
     * @param optionalMap 选填map
     * @param nullWarning null提示
     * @return
     */
    private String buildValidResult(List<String> errorFields, Map<String, Map<String, Object>> optionalMap,
                                    final String nullWarning) {
        final String optionalStr = " one of them must not be empty!";
        for (Entry<String, Map<String, Object>> optionalEntry : optionalMap.entrySet()) {
            Map<String, Object> valueMap = optionalEntry.getValue();
            if (valueMap.size() == 1) {
                Entry<String, Object> next = valueMap.entrySet().iterator().next();
                if (!ObjectUtils.isEmpty(next.getValue())) {
                    continue;
                }
                errorFields.add(next.getKey() + nullWarning);
                continue;
            }
            List<String> filedList = Lists.newLinkedList();
            for (Entry<String, Object> entry : valueMap.entrySet()) {
                if (!ObjectUtils.isEmpty(entry.getValue())) {
                    filedList = Lists.newLinkedList();
                    break;
                }
                filedList.add(entry.getKey());
            }
            if (CollectionUtils.isNotEmpty(filedList)) {
                errorFields.add(StringUtils.join(filedList, ",") + optionalStr);
            }
        }
        if (CollectionUtils.isEmpty(errorFields)) {
            return "";
        }
        return errorFields.toString();
    }

    private String buildApiFiledName(String filedName) {
        String[] filedNames = StringUtils.splitByCharacterTypeCamelCase(filedName);
        Iterator<String> nameIt = Arrays.asList(filedNames).iterator();
        StringBuilder builder = new StringBuilder();
        builder.append(nameIt.next());
        while (nameIt.hasNext()) {
            builder.append("_").append(StringUtils.lowerCase(nameIt.next()));
        }
        return builder.toString();
    }
}
